package com.smile.frame.workflow.flowable.demo.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smile.frame.workflow.flowable.demo.constants.BpmConstants;
import com.smile.frame.workflow.flowable.demo.constants.BpmDeleteReasonEnum;
import com.smile.frame.workflow.flowable.demo.constants.BpmProcessInstanceStatusEnum;
import com.smile.frame.workflow.flowable.demo.service.BpmProcessInstanceService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author LGC
 */
@Slf4j
@Service
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {

    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private IdentityService identityService;
    @Autowired
    private HistoryService historyService;

    @Override
    public void queryProcessInstance(String tenantId, String userId, String processInstanceName,
                                     String processDefinitionId, String businessKey,
                                     String category, Integer status,
                                     Date startTime, Date endTime, Integer pageNo, Integer pageSize) {
        HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .includeProcessVariables()// 左关联ACT_HI_VARINST历史流程变量
                .orderByProcessInstanceStartTime().desc();
        if (StrUtil.isNotBlank(tenantId)) {
            processInstanceQuery.processInstanceTenantId(tenantId);
        }
        // 发起人
        if (StrUtil.isNotBlank(userId)) {
            processInstanceQuery.startedBy(userId);
        }
        if (StrUtil.isNotBlank(processInstanceName)) {
            processInstanceQuery.processInstanceNameLike("%" + processInstanceName + "%");
        }
        if (StrUtil.isNotBlank(processDefinitionId)) {
            processInstanceQuery.processDefinitionId("%" + processDefinitionId + "%");
        }
        if (StrUtil.isNotBlank(businessKey)) {
            processInstanceQuery.processInstanceBusinessKey(businessKey);
        }
        if (StrUtil.isNotBlank(category)) {
            processInstanceQuery.processDefinitionCategory(category);
        }
        // 流程实例变量自定义状态查询
        if (status != null) {
            processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status);
        }
        if (ObjectUtil.isNotNull(startTime) && ObjectUtil.isNotNull(endTime)) {
            // yyyy-MM-dd HH:mm:ss
            processInstanceQuery.startedAfter(startTime);
            processInstanceQuery.startedBefore(endTime);
        }
        // 查询数量
        long processInstanceCount = processInstanceQuery.count();
        if (processInstanceCount == 0) {
            log.info("分页查询流程实例:null");
            return;
        }
        // 查询列表
        List<HistoricProcessInstance> processInstances = processInstanceQuery.listPage((pageNo - 1) * pageSize, pageSize);
        log.info("分页查询流程实例:{}", JSONUtil.toJsonStr(processInstances));
    }

    @Override
    public ProcessInstance getProcessInstance(String processInstanceId) {
        return runtimeService.createProcessInstanceQuery()
                .includeProcessVariables()
                .processInstanceId(processInstanceId)
                .singleResult();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessInstance startProcessByKey(String tenantId, String userId, String key, String businessKey, Map<String, Object> variables) {
        // 设置流程发起人 可以web过滤器进行处理，而不用在这里手动处理
        if (StrUtil.isNotBlank(userId)) {
//            identityService.setAuthenticatedUserId(userId);
            Authentication.setAuthenticatedUserId(userId);
        }
        ProcessInstance instance = runtimeService.startProcessInstanceByKeyAndTenantId(key, businessKey, variables, tenantId);
        log.info("流程实例ID:{},流程定义ID:{},业务标识:{}", instance.getId(), instance.getProcessDefinitionId(), instance.getBusinessKey());
        return instance;
    }

    /**
     * 删除流程实例，正则执行任务 ACT_RU_TASK. 任务会被删除。
     *
     * @param processDefinitionId
     * @param reason
     */
    private void deleteProcessInstance(String processDefinitionId, String reason) {
        runtimeService.deleteProcessInstance(processDefinitionId, reason);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelProcessInstance(String userId, String processInstanceId, String reason) {
        // 1.1 校验流程实例存在
        ProcessInstance instance = getProcessInstance(processInstanceId);
        if (instance == null) {
            throw new RuntimeException("流程实例不存在");
        }
        // 1.2 只能取消自己的
        if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
            throw new RuntimeException("该流程不是你发起的");
        }

        // 2. 通过删除流程实例，实现流程实例的取消,
        // 删除流程实例，正则执行任务 ACT_RU_TASK. 任务会被删除。
        deleteProcessInstance(processInstanceId,
                BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(reason));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProcessInstanceReject(String processInstanceId, String reason) {
        // 1. 更新流程实例 status
        runtimeService.setVariable(processInstanceId, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus());

        // 2. 删除流程实例，以实现驳回任务时，取消整个审批流程 删除流程实例，正则执行任务 ACT_RU_TASK. 任务会被删除。
        deleteProcessInstance(processInstanceId, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));
    }


}
